/*
******************************************************************************
* @File			: sin_cos_tabular.h
* @Brief		: sin and cos computation using a sin table
* @Author		: adqeor
* @Time			: Dec. 20, 2020
* @Version		: 0.1
********************************************************************************
* @ReleaseNotes:
* Feb. 17, 2021
* v0.2, Feb. 17, 2021
* 调整文档
* v0.1, Dec. 20, 2020, by adqeor
* Initial release. Documentation created in zh-ch and en.
* 首次发布, 并建立了中英文的文档.
********************************************************************************
* @TheoryOfOperation:
* Compute sin and cos utilizing a 1st quardrature sin table. Conversions from
* sin/cos of arbitrary angles to sin value on [0, pi/2] is performed in 
* sin_tabular(float) and cos_tabular(float), then the corresponding value is
* passed to sin_table(float), where there stores a static 31-element float32
* sin table. The sin_table returns linear interpolated value between two nearest points.
* @工作原理:
* 在sin_tabular(float)/cos_tabular(float)中, 任意角的正/余弦转换为一象限角的正弦值,
* 随后在sin_table(float)中利用一张31点的静态单精度浮点正弦表进行线性插值.
********************************************************************************
* @ImplementationNotes:
* This functionality utilizes no special FPU instruction.
* Functions have been tested on desktop (TDM-GCC 4.9.2 64-bit), arm-based system
* (STM32F407 compiled with armcc 6.12).
* Modify sin_tabular for customized balance between precision and size.
* Behavior of (int) float/float may vary over platforms.
* @应用说明:
* 不使用特殊的FPU指令.
* 在桌面TDM-GCC 4.9.2 64-bit和armcc 6.12上通过了测试, 但浮点除法转换为符号整数的行为
* 可能与平台相关. 上述计算在sin_tabular和sin_table中出现.
* 可自行修改sin_tabular函数以获得精度和体积的平衡.
********************************************************************************
* @Performance&Precision:
* Maximum of relative error appears over k*pi+pi/2, \pm 350ppm approx.
* The error is not monotinic, due to the interpolating process.
* @性能和精度:
* 最大相对误差在k*pi+pi/2处出现, 幅值在正负350ppm左右. 由于插值过程, 误差不是单调的.
********************************************************************************
*/

#include "stdint.h"
// for typename int32_t, etc. required for desktop.

#ifndef M_PI
#define M_PI		3.14159265358979323846
#define M_PI_2		1.57079632679489661923
#define M_PI_4		0.78539816339744830962
#endif
// Constants copied from math.h
// Conditional define, in case of redefining

float sin_tabular(float x); 	// public method
float cos_tabular(float x); 	// public method
float sin_table(float x); 		// private method

float sin_tabular(float x)
{
	int32_t periods = (int32_t)(x / (2 * M_PI));
	if (x < 0) periods -= 1;
	x -= periods * (2 * M_PI);

	if (x < M_PI_2)
	{
		return sin_table(x);
	}
	else if (x < M_PI)
	{
		return sin_table(M_PI - x);
	}
	else if (x < (M_PI + M_PI_2))
	{
		return -1*sin_table(x - M_PI);
	}
	else
	{
		return -1*sin_table(2*M_PI - x);
	}
}

float cos_tabular(float x)
{
	return sin_tabular(x + M_PI_2);
}

float sin_table(float x)
{
	//if (! (0 <= x && x <= M_PI_2)) printf("%f", x);
	static const float SIN_TABLE_STEP = M_PI / 60;
	static const float SIN_TABLE_1_QUARD[] = \
	{0, \
	0.0523359562429438, \
	0.104528463267653, \
	0.156434465040231, \
	0.207911690817759, \
	0.258819045102521, \
	0.309016994374947, \
	0.358367949545300, \
	0.406736643075800, \
	0.453990499739547, \
	0.500000000000000, \
	0.544639035015027, \
	0.587785252292473, \
	0.629320391049837, \
	0.669130606358858, \
	0.707106781186548, \
	0.743144825477394, \
	0.777145961456971, \
	0.809016994374948, \
	0.838670567945424, \
	0.866025403784439, \
	0.891006524188368, \
	0.913545457642601, \
	0.933580426497202, \
	0.951056516295154, \
	0.965925826289068, \
	0.978147600733806, \
	0.987688340595138, \
	0.994521895368273, \
	0.998629534754574, \
	1};
	
	uint32_t step_count = (uint32_t)(x / SIN_TABLE_STEP);	// x在第一象限占据区间的个数，向下取整
	float residual_ratio = x / SIN_TABLE_STEP - step_count;	// 在两个区间中的余项占区间长的比例

	return (1 - residual_ratio) * SIN_TABLE_1_QUARD[step_count]  + residual_ratio * SIN_TABLE_1_QUARD[step_count+1];
}